Skip to content

fix(tenant-manager/redis): add CACertBase64 to TenantPubSubRedisConfig#491

Merged
jeffersonrodrigues92 merged 1 commit into
mainfrom
hotfix/tm-redis-cacert
May 29, 2026
Merged

fix(tenant-manager/redis): add CACertBase64 to TenantPubSubRedisConfig#491
jeffersonrodrigues92 merged 1 commit into
mainfrom
hotfix/tm-redis-cacert

Conversation

@jeffersonrodrigues92
Copy link
Copy Markdown
Contributor

Bug

commons/tenant-manager/redis/client.go (v5.3.1) builds a tls.Config with no RootCAs when cfg.TLS=true, so Go falls back to the system trust pool. On macOS the system trust path uses Apple's Security Framework, which rejects valid Amazon-issued ElastiCache/Valkey certs as "certificate is not standards compliant" — a strictness mismatch with Go's pure-Go verifier when the same chain is supplied via an explicit RootCAs pool.

Empirical evidence (Valkey 7.2.4 / ElastiCache, TLS-required, default-user auth)

Cluster sample: master.tenant-manager-devops-valkey.1tj5cc.sae1.cache.amazonaws.com:6379

Mode Result
openssl s_client -CAfile amazon-root-ca-1.pem Verify return code: 0 (ok)
Go tls.Dial with RootCAs: poolFromAmazonRootCA1 PING/PONG works ✅
Go tls.Dial without RootCAs (system trust) "certificate is not standards compliant" ❌

Change

Adds an OPTIONAL CACertBase64 field to TenantPubSubRedisConfig. When set and TLS is enabled, the decoded PEM bundle populates tls.Config.RootCAs. Mirrors the pattern already used by commons/redis (TLSConfig.CACertBase64) and the tenant-manager Mongo client.

type TenantPubSubRedisConfig struct {
    Host         string
    Port         string
    Password     string
    TLS          bool
    CACertBase64 string  // NEW — OPTIONAL
}

Backward compatibility (hard guarantee)

The field is additive and optional. No existing caller breaks.

CACertBase64 TLS Behavior
"" (zero value) false No TLS, no pool — identical to today
"" (zero value) true TLS on, RootCAs left nil → system trust fallback — identical to today
valid base64 PEM true TLS on, RootCAs populated from decoded PEM (the new behavior)
valid base64 PEM false Silently ignored (TLS off)
invalid base64 true Wrapped decode error (fail-fast)
valid base64 / not PEM true "no PEM blocks found" error

A regression test explicitly asserts the empty-CA + TLS-on path keeps RootCAs nil, so future refactors can't silently break system-trust callers.

Test matrix

All in commons/tenant-manager/redis/client_cacert_test.go, build tag //go:build unit, deterministic (self-signed ECDSA P-256 CA generated in-test — no public CA bundle embedded):

  • TestBuildOptions_CACertBase64_EmptyWithTLS_PreservesSystemTrust — regression guard for the backward-compat invariant.
  • TestBuildOptions_CACertBase64_ValidPEM_PopulatesRootCAs — happy path, RootCAs.Equal() against the supplied cert.
  • TestBuildOptions_CACertBase64_InvalidBase64_ReturnsWrappedError — fail-fast decode error.
  • TestBuildOptions_CACertBase64_ValidBase64_NotPEM_ReturnsError — fail-fast PEM error.
  • TestBuildOptions_CACertBase64_IgnoredWhenTLSDisabled — TLS off is honored.
  • TestBuildOptions_CACertBase64_TableDriven — full 6-case matrix.

The 4 pre-existing tests in client_test.go and coverage_boost_test.go pass unchanged.

Verification

go build ./...                                                   — clean
go vet ./...                                                     — clean
golangci-lint run ./...                                          — no issues
go test -tags=unit -count=1 ./commons/tenant-manager/redis/...   — 22/22 pass
go test -tags=unit -count=1 ./...                                — 4549/4549 pass across 49 packages

Downstream impact

plugin-br-bank-transfer (and any other consumer with the same macOS-vs-AWS-CA pain) will:

  1. Bump lib-commons to the new patch tag once released from main.
  2. Read a new MULTI_TENANT_REDIS_CA_CERT env var (base64-encoded Amazon Root CA 1 PEM).
  3. Pass that value to CACertBase64 when building TenantPubSubRedisConfig.

The new field's GoDoc explicitly names MULTI_TENANT_REDIS_CA_CERT as the canonical downstream env to keep wiring consistent.

Hotfix logistics

  • Target branch: main (hotfix), patch bump expected via semantic-release on fix: commit type → next tag should be v5.3.2.
  • CHANGELOG.md: intentionally not edited in this PR — semantic-release regenerates it post-merge per .releaserc.yml.
  • Back-merge to develop: required as a follow-up so the field reaches the next minor pre-release. The repo's standard develop-backmerge workflow (chore(changelog): backmerge main into develop) should handle it; if not, a follow-up PR main → develop is needed. Not part of this PR.

Constraints respected

  • Default behavior unchanged.
  • No panics; %w error wrapping; ctx-first preserved on the NewTenantPubSubRedisClient signature.
  • Field-style config (consistent with the existing TenantPubSubRedisConfig shape).
  • Commit signed (-S) with trailer X-Lerian-Ref: 0x1.

Adds an OPTIONAL CACertBase64 field on TenantPubSubRedisConfig so callers
can supply a base64-encoded PEM bundle that populates tls.Config.RootCAs
when TLS is enabled. Mirrors the pattern already used by commons/redis
and the tenant-manager Mongo client.

Why: on macOS the system trust path uses the Security Framework, which
rejects valid Amazon-issued ElastiCache/Valkey certificates as
"certificate is not standards compliant" — a strictness mismatch with
Go's pure-Go verifier. openssl s_client -CAfile amazon-root-ca-1.pem
verifies the same cluster successfully, and Go tls.Dial with an
explicit RootCAs pool (Amazon Root CA 1 PEM) handshakes cleanly. The
plugin needs a way to inject that CA without falling back to the
permissive InsecureSkipVerify path.

Default behavior preserved (backward-compatible):
  - Empty CACertBase64 with TLS=true leaves tls.Config.RootCAs nil so
    the Go runtime continues to use the system trust pool, exactly as
    today. No existing caller breaks.
  - CACertBase64 set with TLS=false is silently ignored.
  - Invalid base64 returns a wrapped decode error.
  - Valid base64 but no PEM blocks returns "no PEM blocks found".

Downstream consumers typically wire the new field from a
MULTI_TENANT_REDIS_CA_CERT environment variable.

Tests: table-driven unit tests in client_cacert_test.go cover all six
matrix cells (TLS x CACertBase64 presence/validity) including the
explicit regression assertion that empty CACertBase64 + TLS=true keeps
RootCAs nil. A small self-signed CA is generated in-test (ECDSA P-256)
so the suite stays deterministic and free of public CA bundles. The
four pre-existing tests in client_test.go and coverage_boost_test.go
are unchanged and still pass.

X-Lerian-Ref: 0x1
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6ba46b83-0aa7-49a4-8e1f-8d4c48023812

📥 Commits

Reviewing files that changed from the base of the PR and between 13a601b and 7036e8a.

📒 Files selected for processing (2)
  • commons/tenant-manager/redis/client.go
  • commons/tenant-manager/redis/client_cacert_test.go

Walkthrough

This PR extends Redis TLS configuration to support custom CA certificates through a base64-encoded PEM field. The implementation refactors TLS option building into a dedicated helper function, handles base64 decoding and certificate pool creation, and includes comprehensive test coverage for all configuration paths.

Changes

Custom CA Certificate Support for Redis TLS

Layer / File(s) Summary
Config contract and dependencies
commons/tenant-manager/redis/client.go
Adds crypto/x509 import and extends TenantPubSubRedisConfig with optional CACertBase64 field for custom CA certificate override.
TLS configuration implementation
commons/tenant-manager/redis/client.go
Implements buildTLSConfig helper that decodes base64 PEM certificates into an x509.CertPool and sets RootCAs when provided; BuildOptions refactored to use this helper with explicit error handling for invalid base64 and missing PEM blocks.
Test coverage for CA certificate handling
commons/tenant-manager/redis/client_cacert_test.go
Provides test utilities for self-signed CA generation, individual focused tests validating system trust preservation and valid PEM loading, error case validation for invalid base64 and malformed certificates, and a table-driven test suite covering all TLS and CACertBase64 configuration combinations.

Comment @coderabbitai help to get the list of available commands and usage tips.

@lerian-studio
Copy link
Copy Markdown
Contributor

🔍 PR Validation Summary

🚫 PR Blocked — 1 blocking failure

Check Status Blocking
Source Branch ✅ success yes
PR Title ❌ failure yes
PR Description ✅ success yes
PR Size ⏭️ skipped no
Auto Labels ⏭️ skipped no
PR Metadata ⏭️ skipped no

Fix the blocking checks above before merge.


🔍 View workflow run

@lerian-studio
Copy link
Copy Markdown
Contributor

🔒 Security Scan Results — lib-commons

✅ PR Mergeable — no blocking findings

Stage Status Blocking?
Filesystem Scan ✅ Clean
Docker Image Scan ➖ Skipped
Docker Hub Health Score ➖ Skipped
Pre-release Version Check ✅ Clean

Trivy

Filesystem Scan

✅ No vulnerabilities or secrets found.


Pre-release Version Check

✅ No unstable version pins found.


🔍 View full scan logs

@jeffersonrodrigues92 jeffersonrodrigues92 merged commit 5fe4ce6 into main May 29, 2026
17 of 19 checks passed
@lerian-studio
Copy link
Copy Markdown
Contributor

📊 Unit Test Coverage Report: app

Metric Value
Overall Coverage 87.0% ✅ PASS
Threshold 80%

Coverage by Package

Package Coverage
github.com/LerianStudio/lib-commons/v5/commons/backoff 91.1%
github.com/LerianStudio/lib-commons/v5/commons/certificate 88.8%
github.com/LerianStudio/lib-commons/v5/commons/circuitbreaker 86.8%
github.com/LerianStudio/lib-commons/v5/commons/cron 94.2%
github.com/LerianStudio/lib-commons/v5/commons/crypto 95.6%
github.com/LerianStudio/lib-commons/v5/commons/dlq 81.0%
github.com/LerianStudio/lib-commons/v5/commons/errgroup 86.1%
github.com/LerianStudio/lib-commons/v5/commons/internal/nilcheck 100.0%
github.com/LerianStudio/lib-commons/v5/commons/jwt 89.4%
github.com/LerianStudio/lib-commons/v5/commons/license 96.9%
github.com/LerianStudio/lib-commons/v5/commons/mongo 89.0%
github.com/LerianStudio/lib-commons/v5/commons/net/http/idempotency 93.0%
github.com/LerianStudio/lib-commons/v5/commons/net/http/ratelimit 90.8%
github.com/LerianStudio/lib-commons/v5/commons/net/http 96.1%
github.com/LerianStudio/lib-commons/v5/commons/outbox 91.9%
github.com/LerianStudio/lib-commons/v5/commons/pointers 100.0%
github.com/LerianStudio/lib-commons/v5/commons/postgres 84.6%
github.com/LerianStudio/lib-commons/v5/commons/rabbitmq 89.3%
github.com/LerianStudio/lib-commons/v5/commons/redis 89.5%
github.com/LerianStudio/lib-commons/v5/commons/safe 99.6%
github.com/LerianStudio/lib-commons/v5/commons/secretsmanager 98.7%
github.com/LerianStudio/lib-commons/v5/commons/security/ssrf 95.9%
github.com/LerianStudio/lib-commons/v5/commons/security 100.0%
github.com/LerianStudio/lib-commons/v5/commons/server 88.0%
github.com/LerianStudio/lib-commons/v5/commons/tenant-manager/cache 97.9%
github.com/LerianStudio/lib-commons/v5/commons/tenant-manager/client 93.5%
github.com/LerianStudio/lib-commons/v5/commons/tenant-manager/consumer 87.9%
github.com/LerianStudio/lib-commons/v5/commons/tenant-manager/core 99.0%
github.com/LerianStudio/lib-commons/v5/commons/tenant-manager/event 95.7%
github.com/LerianStudio/lib-commons/v5/commons/tenant-manager/internal/eviction 100.0%
github.com/LerianStudio/lib-commons/v5/commons/tenant-manager/log 100.0%
github.com/LerianStudio/lib-commons/v5/commons/tenant-manager/middleware 92.3%
github.com/LerianStudio/lib-commons/v5/commons/tenant-manager/mongo 76.3%
github.com/LerianStudio/lib-commons/v5/commons/tenant-manager/postgres 86.8%
github.com/LerianStudio/lib-commons/v5/commons/tenant-manager/rabbitmq 82.6%
github.com/LerianStudio/lib-commons/v5/commons/tenant-manager/redis 95.8%
github.com/LerianStudio/lib-commons/v5/commons/tenant-manager/s3 96.3%
github.com/LerianStudio/lib-commons/v5/commons/tenant-manager/tenantcache 98.4%
github.com/LerianStudio/lib-commons/v5/commons/tenant-manager/valkey 100.0%
github.com/LerianStudio/lib-commons/v5/commons/transaction 95.1%
github.com/LerianStudio/lib-commons/v5/commons/webhook 91.5%
github.com/LerianStudio/lib-commons/v5/commons 96.4%

Generated by Go PR Analysis workflow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants